﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AmiEmuLib.AmigaMachine;
using System.Diagnostics;
using AmiEmuLib.AmigaMachine.AmigaMemory;
using System.Xml;
using TomanuExtensions;

namespace AmiEmuLib.SaveState
{
    /// <summary>
    /// Part of memory bank. Continous memory range.
    /// In diff mode there can be space between ranges.
    /// </summary>
    public abstract class MemoryRange
    {
        /// <summary>
        /// Range size.
        /// </summary>
        public MemorySize Size { get; protected set; }

        /// <summary>
        /// Range address.
        /// </summary>
        [DebuggerDisplay("{Hex.UIntToHex(Address)}")]
        public uint Address { get; protected set; }

        /// <summary>
        /// Parent bank state.
        /// </summary>
        public MemoryBankState BankState { get; internal set; }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_reader"></param>
        /// <param name="a_bank_state"></param>
        internal MemoryRange(XmlReader a_reader, MemoryBankState a_bank_state)
        {
            Address = a_reader.GetAttributeHexUInt("Address");
            Size = a_reader.GetAttribute("Size");
            BankState = a_bank_state;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="a_bank_state"></param>
        /// <param name="a_address"></param>
        /// <param name="a_size"></param>
        internal MemoryRange(MemoryBankState a_bank_state, uint a_address, MemorySize a_size)
        {
            BankState = a_bank_state;
            Address = a_address;
            Size = a_size;
        }

        /// <summary>
        /// For decriptive reasons.
        /// </summary>
        public MemoryBankType BankType
        {
            get
            {
                return BankState.BankType;
            }
        }

        /// <summary>
        /// Check that cell address is in range.
        /// </summary>
        /// <param name="a_address"></param>
        /// <returns></returns>
        public bool InRange(uint a_address)
        {
            if (!MemoryHelpers.IsWordAllign(a_address))
                throw new ArgumentException();

            return (a_address >= Address) && ((a_address - Address) < Size);
        }

        /// <summary>
        /// Last range address word.
        /// </summary>
        public uint LastWordAddress
        {
            get
            {
                return Address + Size - 2;
            }
        }

        /// <summary>
        /// Numbers of char (with end lines and indentation spaces) of xml content.
        /// </summary>
        /// <returns></returns>
        internal abstract uint GetCharSize();

        /// <summary>
        /// Get word from adress. Valid only after load or persist.
        /// </summary>
        /// <param name="a_address"></param>
        /// <returns></returns>
        internal abstract ushort GetWord(uint a_address);

        /// <summary>
        /// Restore range from memory.
        /// </summary>
        /// <param name="a_memory"></param>
        internal abstract void Restore(Memory a_memory);

        /// <summary>
        /// Persists range from memory. Used at the end of bank state construction after 
        /// range optimizations.
        /// </summary>
        /// <param name="a_memory"></param>
        internal abstract void Persist(MemoryBank a_bank);

        /// <summary>
        /// Write xml.
        /// </summary>
        /// <param name="a_writer"></param>
        internal virtual void WriteXml(XmlWriter a_writer)
        {
            a_writer.WriteHexAttribute("Address", Address);
            a_writer.WriteAttribute("Size", Size);
        }

        /// <summary>
        /// Read memory range from xml.
        /// </summary>
        /// <param name="a_reader"></param>
        /// <returns></returns>
        internal static MemoryRange Create(XmlReader a_reader, MemoryBankState a_bank_state)
        {
            if (a_reader.Name == "Area")
                return new AreaMemoryRange(a_reader, a_bank_state);
            else if (a_reader.Name == "Fill")
                return new FillMemoryRange(a_reader, a_bank_state);
            else
                throw new XmlException();
        }
    }
}
